package cn.marknum.gateway.filter;

import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.net.URLEncoder;
import cn.hutool.core.util.StrUtil;
import cn.marknum.web.ErrCode;
import cn.marknum.web.exception.CustomException;
import cn.marknum.web.exception.MissingArgsException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 用于App请求的重定向
 *
 * @author zhangzhao    2022-08-16 09:43
 */
public class AppRedirectFilter implements WebFilter {
    private Logger logger = LoggerFactory.getLogger(AppRedirectFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        URI uri = exchange.getRequest().getURI();
        HttpMethod method = exchange.getRequest().getMethod();
        if (method == HttpMethod.POST && "/api/app/redirect".equals(uri.getPath())) {
            return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {

                String reqBody = dataBuffer.toString(StandardCharsets.UTF_8);
                JSONObject jo = JSON.parseObject(reqBody);
                String messageId = jo.getString("messageId");
                String version = jo.getString("version");
                JSONArray queryParams = jo.getJSONObject("parameter").getJSONObject("condition").getJSONArray("keyValueList");
                logger.info("=====fields========{}", jo.getJSONObject("parameter").getString("fields"));
                try {
                    if (!queryParams.isEmpty()) {
                        return chain.filter(exchange.mutate()
                                .request(buildRequest(exchange, queryParams))
                                .response(buildResponse(exchange, messageId, version))
                                .build());
                    }
                    MissingArgsException e = new MissingArgsException(ErrCode.MISSING_ARGS.getTipCode(), ErrCode.MISSING_ARGS.getTipMsg());
                    e.addArgNames("所有必要");
                    throw e;
                } catch (CustomException e) {
                    return sendMessage(exchange.getResponse(), messageId, version, e.getTipCode(), e.getTipMsg());
                }
            });
        }
        return chain.filter(exchange);
    }

    private Mono<Void> sendMessage(ServerHttpResponse serverHttpResponse, String messageId, String version, String code, String message) {
        serverHttpResponse.getHeaders().set("Content-Type", "application/json;charset=UTF-8");
        serverHttpResponse.setStatusCode(HttpStatus.OK);
        StringBuilder s = new StringBuilder();
        s.append("{");
        s.append("\"code\":\"");
        s.append(code);
        s.append("\",\"message\":\"");
        s.append(message);
        s.append("\"}");
        DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(
                JSON.toJSONString(new AppResult(messageId, version, s.toString(), null)).getBytes(StandardCharsets.UTF_8)
        );
        return serverHttpResponse.writeWith(Flux.just(dataBuffer));
    }

    /**
     * 修改请求
     *
     * @param exchange
     * @param queryParams
     * @return
     */
    private ServerHttpRequest buildRequest(ServerWebExchange exchange, JSONArray queryParams) {
        URI uri = exchange.getRequest().getURI();
        String path = null;
        String reqMethod = null;
        String params = null;
        String authorization = null;
        String contentType = null;
        String realXIP = null;
        for (int i = 0; i < queryParams.size(); i++) {
            JSONObject jo = queryParams.getJSONObject(i);
            String key = jo.getString("key");
            if ("path".equalsIgnoreCase(key)) {
                path = jo.getString("value");
            } else if ("method".equalsIgnoreCase(key)) {
                reqMethod = jo.getString("value");
            } else if ("params".equalsIgnoreCase(key)) {
                params = jo.getString("value");
            } else if ("authorization".equalsIgnoreCase(key)) {
                authorization = jo.getString("value");
            } else if ("content-type".equalsIgnoreCase(key)) {
                contentType = jo.getString("value");
            } else if ("RealXIP".equalsIgnoreCase(key)) {
                realXIP = jo.getString("value");
            }
        }
        if (StrUtil.isEmpty(path)) {
            MissingArgsException e = new MissingArgsException(ErrCode.MISSING_ARGS.getTipCode(), ErrCode.MISSING_ARGS.getTipMsg());
            e.addArgNames("所有必要");
            throw e;
        }
        if (StrUtil.isEmpty(reqMethod)) {
            reqMethod = "post";
        }
        HttpMethod method;
        DataBuffer body = null;
        int len = 0;
        //转发真正的请求地址
        StringBuilder s = new StringBuilder();
        s.append(uri.getScheme());
        s.append("://");
        s.append(uri.getHost());
        s.append(":");
        s.append(uri.getPort());
        s.append(path);
        if ("get".equalsIgnoreCase(reqMethod)) {
            method = HttpMethod.GET;
            if (params != null) {
                s.append("?");
                JSONObject jop = JSONObject.parseObject(params);
                for (Map.Entry<String, Object> entry : jop.entrySet()) {
                    s.append(entry.getKey());
                    s.append("=");
                    s.append(entry.getValue());
                    s.append("&");
                }
                s.setLength(s.length() - 1);
            }
        } else {
            method = HttpMethod.POST;
            if (params != null) {
                if ("x-www-form-urlencoded".equalsIgnoreCase(contentType)) {
                    JSONObject jo = JSONObject.parseObject(params);
                    StringBuilder b = new StringBuilder();
                    for (Map.Entry<String, Object> entry : jo.entrySet()) {
                        if (entry.getValue() instanceof JSONArray) {
                            JSONArray ja = (JSONArray) entry.getValue();
                            if (ja.size() == 0) {
                                b.append(entry.getKey());
                                b.append("=");
                            } else {
                                for (int i = 0; i < ja.size(); i++) {
                                    b.append(entry.getKey());
                                    b.append("=");
                                    Object val = ja.get(i);
                                    if (val != null && !"".equals(String.valueOf(val))) {
                                        b.append(URLEncoder.createAll().encode(String.valueOf(val), StandardCharsets.UTF_8));
                                        b.append("&");
                                    }
                                }
                            }
                        } else {
                            b.append(entry.getKey());
                            b.append("=");
                            b.append(URLEncoder.createAll().encode(entry.getValue().toString(), StandardCharsets.UTF_8));
                            b.append("&");
                        }
                    }
                    b.setLength(b.length() - 1);
                    params = b.toString();
                    logger.debug("拼接的请求参数为：{}", params);
                }
                byte[] raw = params.getBytes(StandardCharsets.UTF_8);
                len = raw.length;
                body = exchange.getResponse().bufferFactory().wrap(raw);
            }
        }
        String url = s.toString();
        logger.info("真实访问信息：[method:{}],[url:{}],[params:{}],[contentType:{}],[authorization:{}],[realXIP:{}]", method.name(), url, params, contentType, authorization,realXIP);
        ServerHttpRequest.Builder newRequestBuilder = exchange.getRequest().mutate().uri(URI.create(url))
                .header("Content-Length", String.valueOf(len))
                .header("Content-Type", (method == HttpMethod.GET || "x-www-form-urlencoded".equals(contentType)) ?
                        "application/x-www-form-urlencoded" : "application/json;charset=utf8")
                .method(method);
        if (authorization != null) {
            newRequestBuilder.header("Authorization", authorization);
        }
        if (realXIP!=null){
            newRequestBuilder.header("RealXIP", realXIP);
        }
        Flux<DataBuffer> bodyFlux = body == null ? Flux.empty() : Flux.just(body);
        return new ServerHttpRequestDecorator(newRequestBuilder.build()) {
            @Override
            public Flux<DataBuffer> getBody() {
                return bodyFlux;
            }
        };
    }

    /**
     * 构造响应结果，对所有请求的响应结果进行二次封装，封装为如下结构
     * {
     * code:0
     * data:[
     * {
     * field:"content",
     * value:"实际返回内容"
     * }
     * ]
     * }
     *
     * @param exchange
     * @return
     */
    private ServerHttpResponse buildResponse(ServerWebExchange exchange, String messageId, String version) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;

                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {

                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);

                        byte[] content = new byte[join.readableByteCount()];

                        join.read(content);
                        // 释放掉内存
                        DataBufferUtils.release(join);
                        String contentStr = new String(content, StandardCharsets.UTF_8);
                        String authorization = exchange.getResponse().getHeaders().getFirst("Authorization");
                        String resultStr = JSON.toJSONString(new AppResult(messageId, version, contentStr, authorization));
                        logger.info("转发响应结果======url:{}======result:{}", exchange.getRequest().getURI().toString(), resultStr);
                        byte[] result = resultStr.getBytes(StandardCharsets.UTF_8);

                        originalResponse.getHeaders().setContentLength(result.length);

                        return bufferFactory.wrap(result);
                    }));

                }
                // if body is not a flux. never got there.
                return super.writeWith(body);
            }
        };
        return decoratedResponse;
    }

    class AppResult {
        public String messageId;
        public String version;
        public String code = "200";
        public String message = "ok";

        public AppResult(String messageId, String version, String value, String authorization) {
            this.messageId = messageId;
            this.version = version;
            Map<String, Integer> pageable = new HashMap<>();
            pageable.put("pageSize", 1);
            pageable.put("pageNo", 1);
            pageable.put("total", 1);
            data.put("page", pageable);
            List<Map<String, List<Map<String, String>>>> dataList = new ArrayList<>(2);
            data.put("dataList", dataList);
            Map<String, List<Map<String, String>>> map = new HashMap<>();
            dataList.add(map);
            List<Map<String, String>> fieldValues = new ArrayList<>(2);
            map.put("fieldValues", fieldValues);
            Map<String, String> keyValue = new HashMap<>();
            fieldValues.add(keyValue);
            keyValue.put("field", "content");
            keyValue.put("value", value);
            if (StrUtil.isNotEmpty(authorization)) {
                keyValue = new HashMap<>();
                fieldValues.add(keyValue);
                keyValue.put("field", "authorization");
                keyValue.put("value", authorization);
            }
        }

        public Map<String, Object> data = new HashMap<>();
    }
}
